﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace M3G {
    public class Stencil : Object3D {

        #region Constant
        // Stencil Operation
        public const int Zero     = 64;
        public const int Keep     = 65;
        public const int Replace  = 66;
        public const int Incr     = 67;
        public const int Decr     = 68;
        public const int Invert   = 69;
        public const int IncrWrap = 70;
        public const int DecrWrap = 71;
        // Face
        public const int Front    = 131072;
        public const int Back     = 262144;
        #endregion

        #region Field
        Dictionary<int,uint> stencilWriteMask;
        Dictionary<int, int> comparisonFunction;
        Dictionary<int,uint> comparisonMask;
        Dictionary<int, int> comparisonReferenceValue;
        Dictionary<int, int> stencilFailOperator;
        Dictionary<int, int> stencilPassDepthFailOperator;
        Dictionary<int, int> stencilPassDepthPassOperator;
        #endregion

        #region Method
        public Stencil () {
            this.stencilWriteMask = new Dictionary<int, uint> ();
            this.comparisonFunction = new Dictionary<int, int> ();
            this.comparisonMask = new Dictionary<int, uint> ();
            this.comparisonReferenceValue = new Dictionary<int, int> ();
            this.stencilFailOperator = new Dictionary<int, int> ();
            this.stencilPassDepthFailOperator = new Dictionary<int, int> ();
            this.stencilPassDepthPassOperator = new Dictionary<int, int> ();

            // Front
            this.stencilWriteMask.Add (Front, 0xffffffffu);
            this.comparisonFunction.Add (Front, CompositingMode.Always);
            this.comparisonMask.Add (Front, 0xffffffffu);
            this.comparisonReferenceValue.Add (Front, 0);
            this.stencilFailOperator.Add (Front, Keep);
            this.stencilPassDepthFailOperator.Add (Front, Keep);
            this.stencilPassDepthPassOperator.Add (Front, Keep);

            // Back
            this.stencilWriteMask.Add (Back, 0xffffffffu);
            this.comparisonFunction.Add (Back, CompositingMode.Always);
            this.comparisonMask.Add (Back, 0xffffffffu);
            this.comparisonReferenceValue.Add (Back, 0);
            this.stencilFailOperator.Add (Back, Keep);
            this.stencilPassDepthFailOperator.Add (Back, Keep);
            this.stencilPassDepthPassOperator.Add (Back, Keep);
        }

        protected Stencil (Stencil stencil)
            : base (stencil) {
            this.stencilWriteMask = new Dictionary<int, uint> (stencil.stencilWriteMask);
            this.comparisonFunction = new Dictionary<int, int> (stencil.comparisonFunction);
            this.comparisonMask = new Dictionary<int, uint> (stencil.comparisonMask);
            this.comparisonReferenceValue = new Dictionary<int, int> (stencil.comparisonReferenceValue);
            this.stencilFailOperator = new Dictionary<int, int> (stencil.stencilFailOperator);
            this.stencilPassDepthFailOperator = new Dictionary<int, int> (stencil.stencilPassDepthFailOperator);
            this.stencilPassDepthPassOperator = new Dictionary<int, int> (stencil.stencilPassDepthPassOperator);
        }

        public Dictionary<int, int[]> StencilParameters {
            get {
                var dic = new Dictionary<int, int[]> ();
                var frontParameters = new int[]{comparisonFunction          [Front], 
                                                comparisonReferenceValue    [Front],
                                                (int)comparisonMask         [Front],
                                                stencilFailOperator         [Front],
                                                stencilPassDepthFailOperator[Front],
                                                stencilPassDepthPassOperator[Front]};
                var backParameters = new int[]{comparisonFunction           [Back], 
                                                comparisonReferenceValue    [Back],
                                                (int)comparisonMask         [Back],
                                                stencilFailOperator         [Back],
                                                stencilPassDepthFailOperator[Back],
                                                stencilPassDepthPassOperator[Back]};
                dic.Add (Front, frontParameters);
                dic.Add (Back, backParameters);
                return dic;
            }
        }

        public Dictionary<int, uint> StencilWriteMask {
            get {
                return new Dictionary<int, uint> (stencilWriteMask);
            }
        }


        public void SetStencilWriteMask (int face, uint mask) {
            if ((face & ~(Front | Back)) != 0) {
                throw new ArgumentException ("Face must be Front,Back,(Front|Back),face=" + face);
            }

            if ((face & Front) != 0) {
                this.stencilWriteMask[Front] = mask;
            }
            if ((face & Back) != 0) {
                this.stencilWriteMask[Back] = mask;
            }

        }

        public void SetStencilFunc (int face, int func, int refValue, uint mask) {
            if ((face & ~(Front | Back)) != 0) {
                throw new ArgumentException ("Face must be Front,Back,(Front|Back), face=" + face);
            }
            if (func < CompositingMode.Never || func > CompositingMode.Always) {
                throw new ArgumentException ("Func must be Never,Always,Less,LEqual,Equal,GEqual,Greater,NotEqual");
            }

            if ((face & Front) != 0) {
                this.comparisonFunction[Front] = func;
                this.comparisonReferenceValue[Front] = refValue;
                this.comparisonMask[Front] = mask;
            }
            if ((face & Back) != 0) {
                this.comparisonFunction[Back] = func;
                this.comparisonReferenceValue[Back] = refValue;
                this.comparisonMask[Back] = mask;
            }

        }

        public void SetStencilOps (int face, int stencilFailOp, int stencilPassDepthFailOp, int stencilPassDepthPassOp) {
            if ((face & ~(Front | Back)) != 0) {
                throw new ArgumentException ("Face must be Front,Back,(Front|Back), face=" + face);
            }
            if (stencilFailOp < Zero || stencilFailOp > DecrWrap
                || stencilPassDepthFailOp < Zero || stencilPassDepthFailOp > DecrWrap
                || stencilPassDepthFailOp < Zero || stencilPassDepthPassOp > DecrWrap) {
                throw new ArgumentException ("Stencil operation must be Zero,Keep,Replace,Incr,Decr,Invert,IncrWrap,DecrWrap");
            }

            if ((face & Front) != 0) {
                this.stencilFailOperator[Front] = stencilFailOp;
                this.stencilPassDepthFailOperator[Front] = stencilPassDepthFailOp;
                this.stencilPassDepthPassOperator[Front] = stencilPassDepthPassOp;
            }
            if ((face & Back) != 0) {
                this.stencilFailOperator[Back] = stencilFailOp;
                this.stencilPassDepthFailOperator[Back] = stencilPassDepthFailOp;
                this.stencilPassDepthPassOperator[Back] = stencilPassDepthPassOp;
            }

        }

        public override Object3D Duplicate () {
            return new Stencil (this);
        }
        #endregion
    }
}
